This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / web / src / routes / [handle] / +layout.ts
2.8 kB 76 lines
1import { error, redirect } from '@sveltejs/kit'; 2import { createBobbinClient } from '$lib/api/client'; 3import { resolveMiniDoc } from '$lib/api/identity'; 4import { getProfile, type ProfileRecord } from '$lib/api/records'; 5import { count } from '$lib/api/count'; 6import { parallel, toHttpError, httpStatusFor } from '$lib/api/load'; 7import { ClientResponseError } from '$lib/api/client'; 8import { findFollowRkey } from '$lib/api/graph'; 9import type { ProfileCounts } from '$lib/components/profile/types'; 10import type { LayoutLoad } from './$types'; 11 12export const load: LayoutLoad = async (event) => { 13 const parent = await event.parent(); 14 const identifier = decodeURIComponent(event.params.handle); 15 16 // actor identifiers are dids or dotted handles; reject bare words early so 17 // unrelated paths (/settings, /signup, ...) 404 instead of resolving. 18 if (!identifier.startsWith('did:') && !identifier.includes('.')) { 19 error(404, 'Not found'); 20 } 21 22 const ctx = createBobbinClient({ serviceUrl: parent.publicConfig.bobbinUrl, fetch: event.fetch }); 23 const doc = await resolveMiniDoc(ctx, identifier).catch((cause) => 24 toHttpError(cause, 'Could not resolve user') 25 ); 26 27 // canonical url is the handle; redirect dids and stale handles. 28 const canonical = doc.handle && !doc.handle.endsWith('.invalid') ? doc.handle : null; 29 if (canonical && identifier.toLowerCase() !== canonical.toLowerCase()) { 30 redirect(307, `/${canonical}${event.url.search}`); 31 } 32 33 const did = doc.did; 34 const viewerDid = parent.auth?.did; 35 36 let profile: ProfileRecord | null = null; 37 try { 38 profile = (await getProfile(ctx, did)).value; 39 } catch (cause) { 40 if (!(cause instanceof ClientResponseError && httpStatusFor(cause) === 404)) { 41 toHttpError(cause, 'Could not load profile'); 42 } 43 } 44 45 const raw = await parallel({ 46 repos: count(ctx, 'sh.tangled.repo.countRepos', did), 47 strings: count(ctx, 'sh.tangled.string.countStrings', did), 48 stars: count(ctx, 'sh.tangled.feed.countStarsBy', did), 49 followers: count(ctx, 'sh.tangled.graph.countFollows', did), 50 following: count(ctx, 'sh.tangled.graph.countFollowsBy', did), 51 vouches: count(ctx, 'sh.tangled.graph.countVouches', did), 52 viewerFollowRkey: 53 viewerDid && viewerDid !== did 54 ? findFollowRkey(ctx, viewerDid, did).catch(() => null) 55 : Promise.resolve(null) 56 }); 57 58 const counts: ProfileCounts = { 59 repos: raw.repos.count, 60 strings: raw.strings.count, 61 stars: raw.stars.count, 62 followers: raw.followers.count, 63 following: raw.following.count, 64 vouches: raw.vouches.count 65 }; 66 67 const notJoined = !profile && Object.values(counts).every((n) => n === 0); 68 69 return { 70 identity: { did, handle: doc.handle, avatar: doc.avatar }, 71 profile, 72 counts, 73 viewerFollowRkey: raw.viewerFollowRkey, 74 notJoined 75 }; 76};